package easy.trace.plugin.mybatis;

import easy.trace.core.Carrier;
import easy.trace.core.ICarrier;
import easy.trace.core.context.SqlTraceContext;
import easy.trace.core.context.TraceContext;
import easy.trace.core.dto.ExceptionType;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * MyBatis拦截器原理探究
 *
 * https://www.cnblogs.com/fangjian0423/p/mybatis-interceptor.html
 *
 * @author easy
 */
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class })
})
public class MybatisInterceptor implements Interceptor {
    private Properties properties;
    private ICarrier carrier;
    private AtomicBoolean isStartCarrier = new AtomicBoolean(false);

    public MybatisInterceptor() {
        if (!isStartCarrier.get()) {
            carrier = new Carrier();
            isStartCarrier.set(true);
        }
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        SqlTraceContext context = new MybatisContext(invocation);
        Object result;
        if (Optional.ofNullable(carrier).isPresent()) {
            this.carrier.init(context);
            this.carrier.doBeforeHandle();
            try {
                result = invocation.proceed();
            } catch (Throwable e) {
                this.carrier.doEncounterException(new RuntimeException(e.getLocalizedMessage()), ExceptionType.MYSQL);
                throw e;
            } finally {
                this.carrier.doAfterHandle();
            }
        } else {
            result = invocation.proceed();
        }
        return result;
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties0) {
        this.properties = properties0;
    }
}
